package com.jy.rhin.domain.document.template;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jy.common.Encrypter;
import com.jy.common.StringUtil;
import com.jy.framework.base.utils.Converter;
import com.jy.framework.base.utils.Serializer;
import com.jy.rhin.application.infra.assembler.CDADocumentTemplateAssembler;
import com.jy.rhin.domain.exception.RhinInfraErrorType;
import com.jy.rhin.facade.infra.controller.dto.CDADocumentNodeAttributeDto;
import com.jy.rhin.facade.infra.controller.dto.CDADocumentTemplateDto;
import com.jy.rhin.facade.infra.controller.dto.CDADocumentTemplateNodeDto;
import com.jy.rhin.facade.infra.controller.dto.ImportCDADocumentTemplateReq;
import com.jy.rhin.infra.exception.RhinInfraException;
import com.jy.rhin.infra.repo.document.template.po.CDADocumentTemplatePo;
import com.jy.rhin.infra.repo.document.template.query.CDADocumentTemplateQueryHandler;
import com.jy.rhin.infra.uitl.X509TrustUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.w3c.dom.*;
import org.xml.sax.InputSource;

import javax.annotation.Resource;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class CDADocumentTemplateService {

    @Resource
    CDADocumentTemplateQueryHandler cdaDocumentTemplateQueryHandler;
    @Resource
    CDADocumentTemplateAssembler cdaDocumentTemplateAssembler;

    public Page<CDADocumentTemplateDto> pageCDADocumentTemplate(String key, Integer current, Integer size) {
        Page<CDADocumentTemplatePo> page = cdaDocumentTemplateQueryHandler.page(
                new Page<>(current, size),
                Wrappers.<CDADocumentTemplatePo>lambdaQuery()
                        .eq(CDADocumentTemplatePo::getZuoFeiBZ, "0")
                        .and(!StringUtil.isNullOrWhiteSpace(key),
                                item -> item.eq(CDADocumentTemplatePo::getTemplateNo, key).or().eq(CDADocumentTemplatePo::getTemplateName, key))
                        .orderByAsc(CDADocumentTemplatePo::getUpdateTime));
        return cdaDocumentTemplateAssembler.toCDADocumentTemplateDtoPage(page);
    }

    public CDADocumentTemplateNodeDto importCDADocumentTemplate(ImportCDADocumentTemplateReq req) throws Exception {
        Document document = getCDADocumentTemplate(req);
        CDADocumentTemplateNodeDto node = getCDADocumentTemplateNodes(document);
        return node;
    }

    private Document getCDADocumentTemplate(ImportCDADocumentTemplateReq req) throws Exception {
        int i = 0;
        String aliNetwork = req.getUrl();
//        String PATTERN_IP = "(\\d*\\.){3}\\d*";
        String PATTERN_IP = "^http://[^\\s/$.?#].[^\\s]*$";
        Pattern ipPattern = Pattern.compile(PATTERN_IP);
        Matcher matcher = ipPattern.matcher(aliNetwork);
        boolean isHttp = matcher.find();
//        if (isHttp) {
//            aliNetwork = String.format("http://%s", aliNetwork);
//        } else {
//            aliNetwork = String.format("https://%s", aliNetwork);
//        }
        log.info("访问的地址：{}", aliNetwork);
        SSLContext sslcontext = SSLContext.getInstance("SSL", "SunJSSE");
        sslcontext.init(null, new TrustManager[]{new X509TrustUtils()}, new java.security.SecureRandom());

        URL url = new URL(aliNetwork);
        HostnameVerifier ignoreHostnameVerifier = (s, sslsession) -> {
            log.info("Hostname is not matched for cert.");
            return true;
        };

        InputStream inputStream;
        if (isHttp) {
            HttpURLConnection urlCon = (HttpURLConnection) url.openConnection();
            urlCon.setConnectTimeout(6000);
            urlCon.setReadTimeout(6000);
            int code = urlCon.getResponseCode();
            if (code != HttpURLConnection.HTTP_OK) {
                log.info("code值：{}", code);
                throw new RhinInfraException(RhinInfraErrorType.CDADOCUMENTTEMPLATE_FILE_READ_ERROR);
            }
            inputStream = urlCon.getInputStream();
        } else {
            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
            HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
            HttpsURLConnection urlCon = (HttpsURLConnection) url.openConnection();
            urlCon.setConnectTimeout(6000);
            urlCon.setReadTimeout(6000);
            int code = urlCon.getResponseCode();
            if (code != HttpURLConnection.HTTP_OK) {
                log.info("code值：{}", code);
                throw new RhinInfraException(RhinInfraErrorType.CDADOCUMENTTEMPLATE_FILE_READ_ERROR);
            }
            inputStream = urlCon.getInputStream();
        }

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputSource inputSource = new InputSource(inputStream);
        return builder.parse(inputSource);
    }

    public CDADocumentTemplateNodeDto getCDADocumentTemplateNodes(Document document) {
        CDADocumentTemplateNodeDto cdaDocumentTemplateNode = new CDADocumentTemplateNodeDto();
        Element root = document.getDocumentElement();
        traverseElement(root, cdaDocumentTemplateNode, "");
        return cdaDocumentTemplateNode;
    }

    private void traverseElement(Element element, CDADocumentTemplateNodeDto cdaDocumentTemplateNode, String path) {
        cdaDocumentTemplateNode.setName(element.getTagName());
        cdaDocumentTemplateNode.setPath(String.format("%s/%s", path, element.getTagName()));

        List<CDADocumentTemplateNodeDto> children = new ArrayList<>();
        NodeList nodeList = element.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                CDADocumentTemplateNodeDto c = new CDADocumentTemplateNodeDto();
                traverseElement((Element) node, c, cdaDocumentTemplateNode.getPath());
                children.add(c);
            } else if (node instanceof Text) {
                cdaDocumentTemplateNode.setValue(((Text) node).getTextContent().trim());
            }
        }
        cdaDocumentTemplateNode.setChildren(children);

        List<CDADocumentNodeAttributeDto> attributes = new ArrayList<>();
        NamedNodeMap namedNodeMap = element.getAttributes();
        for (int i = 0; i < namedNodeMap.getLength(); i++) {
            Node node = namedNodeMap.item(i);
            CDADocumentNodeAttributeDto attribute = new CDADocumentNodeAttributeDto();
            attribute.setName(node.getNodeName());
            attribute.setValue(node.getNodeValue());
            attributes.add(attribute);
        }
        cdaDocumentTemplateNode.setAttributes(attributes);
    }

    public String previewCDADocumentTemplateByNodes(CDADocumentTemplateNodeDto req) {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        traverseXml(sb, req);
        return sb.toString();
    }

    private void traverseXml(StringBuilder sb, CDADocumentTemplateNodeDto req) {
        String newline = System.getProperty("line.separator");
        sb.append(newline);

        StringBuilder attribute = new StringBuilder();
        for (CDADocumentNodeAttributeDto a : req.getAttributes()) {
            attribute.append(String.format(" %s=\"%s\"", a.getName(), a.getValueType() == null || a.getValueType() == 1 ? Converter.toString(a.getValue()) :
                    String.format("%s.%s", Converter.toString(a.getTable()), Converter.toString(a.getField()))));
        }

        String indent = StringUtil.padLeft("", (req.getPath().split("/").length - 2) * 4, ' ');
        sb.append(indent);
        sb.append(String.format("<%s%s>", req.getName(), attribute.toString()));

        if (req.getChildren().size() > 0) {
            for (CDADocumentTemplateNodeDto n : req.getChildren()) {
                traverseXml(sb, n);
            }
            sb.append(newline);
            sb.append(indent);
        } else {
            sb.append(req.getValueType() == null || req.getValueType() == 1 ? Converter.toString(req.getValue()) :
                    String.format("%s.%s", Converter.toString(req.getTable()), Converter.toString(req.getField())));
        }

        sb.append(String.format("</%s>", req.getName()));
    }

    public void saveCDADocumentTemplate(CDADocumentTemplateDto req) throws Exception {
        CDADocumentTemplatePo po = cdaDocumentTemplateAssembler.fromCDADocumentTemplateDto(req);

        QueryWrapper<CDADocumentTemplatePo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .ne(req.getId() != null, CDADocumentTemplatePo::getId, req.getId())
                .eq(CDADocumentTemplatePo::getTemplateNo, req.getTemplateNo())
                .eq(CDADocumentTemplatePo::getZuoFeiBZ, "0");
        int count = cdaDocumentTemplateQueryHandler.count(queryWrapper);
        if (count > 0) {
            throw new RhinInfraException(RhinInfraErrorType.CDADOCUMENTTEMPLATE_NO_REPEAT);
        }

        po.setTemplateNodes(Serializer.serialize(req.getTemplateNodesJson()));
        po.setTemplateContent(previewCDADocumentTemplateByNodes(req.getTemplateNodesJson()));
        po.setCreateTime(new Date());
        po.setUpdateTime(new Date());

        if (req.getId() == null) {
            cdaDocumentTemplateQueryHandler.save(po);
        } else {
            cdaDocumentTemplateQueryHandler.updateById(po);
        }
    }

    public CDADocumentTemplateDto getCDADocumentTemplate(Long id) {
        CDADocumentTemplatePo po = cdaDocumentTemplateQueryHandler.getById(id);
        CDADocumentTemplateDto dto = cdaDocumentTemplateAssembler.toCDADocumentTemplateDto(po);
        dto.setTemplateNodesJson(Serializer.deserialize(dto.getTemplateNodes(), CDADocumentTemplateNodeDto.class));
        return dto;
    }

    public void deleteCDADocumentTemplate(Long id) {
        CDADocumentTemplatePo po = cdaDocumentTemplateQueryHandler.getById(id);
        po.setZuoFeiBZ("1");
        po.setCreateTime(new Date());
        po.setUpdateTime(new Date());
        cdaDocumentTemplateQueryHandler.updateById(po);
    }

    public String previewCDADocumentTemplate(Long id) {
        CDADocumentTemplatePo po = cdaDocumentTemplateQueryHandler.getById(id);
        return previewCDADocumentTemplateByNodes(Serializer.deserialize(po.getTemplateNodes(), CDADocumentTemplateNodeDto.class));
    }

    public void downloadCDADocumentTemplate(Long id, HttpServletResponse response) throws Exception {
        CDADocumentTemplatePo po = cdaDocumentTemplateQueryHandler.getById(id);
        String xml = previewCDADocumentTemplateByNodes(Serializer.deserialize(po.getTemplateNodes(), CDADocumentTemplateNodeDto.class));

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        try (InputStream inputStream = new ByteArrayInputStream(xml.getBytes())) {
            byte[] buffer = new byte[1024];
            int length;

            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
        } catch (Exception ex) {
            throw new RhinInfraException(RhinInfraErrorType.CDADOCUMENTTEMPLATE_FILE_DOWNLOAD_ERROR);
        }

        // 设置响应头信息
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=" + Encrypter.urlEncode(po.getTemplateName()) + ".xml");

        // 将XML数据写入response输出流中，完成下载操作
        response.getOutputStream().write(outputStream.toByteArray());
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

}
